Going further with CDI 1.2

Antoine Sabot-Durand

  • Senior Software Engineer
  • CDI co-spec lead
  • Red Hat, Inc.
  • @antoine_sd
  • www.next-presso.com
  • github.com/antoinesd

Antonin Stefanutti

  • Software Engineer
  • Murex, SAS
  • @astefanut
  • github.com/astefanutti

Agenda

  • CDI Extensions
  • Legacy Code
  • Camel CDI
  • Metrics CDI

CDI Extensions

Portable Extensions

One of the most powerful feature of the CDI specification
Not really popularized maybe given the high level of abstraction
powerful

Container Metadata

Observer pattern to listen for container initialization lifecycle events
Comprehensive access to and modification of the container metadata model
rubik

Plugin Architecture

Service provider of the service javax.enterprise.inject.spi.Extension declared in META-INF/services
import javax.enterprise.event.Observes;
import javax.enterprise.inject.spi.Extension;

class CdiExtension implements Extension {

    void beforeBeanDiscovery(@Observes BeforeBeanDiscovery bbd) {
    }
    ...

    void afterDeploymentValidation(@Observes AfterDeploymentValidation adv) {
    }
}

Lifecycle Events

lifecycle

Legacy code

Injection points, parameterized types, programmatic bean

Diagram

Camel CDI

Annotated types, events, injection Targets, transactional Observers

Apache Camel

Open-source integration framework based on known Enterprise Integration Patterns
Bean binding and integration with Spring, Blueprint, Guice and CDI
eip

Camel Annotations

@EndpointInject(uri="jms:queue:foo")
Endpoint endpoint;

@PropertyInject(value = "timeout", defaultValue = "5000")
int timeout;

@BeanInject("foo")
FooBean foo;

@Produce(uri = "mock:foo")
ProducerTemplate producer;

@Consume(uri="jms:queue:foo")
void onFoo(@Body String body) {
}
Bring support for both Camel and CDI beans…​

The ProcessAnnotatedType Event

AnnotatedType<X>
public interface AnnotatedType<X> extends Annotated {
    public Class<X> getJavaClass();
    public Set<AnnotatedConstructor<X>> getConstructors();
    public Set<AnnotatedMethod<? super X>> getMethods();
    public Set<AnnotatedField<? super X>> getFields();
}
ProcessAnnotatedType<X>
public interface ProcessAnnotatedType<X> {
    public AnnotatedType<X> getAnnotatedType();
    public void setAnnotatedType(AnnotatedType<X> type);
    public void veto();
}

The ProcessInjectionTarget Event

InjectionTarget<T>
public interface InjectionTarget<T> extends Producer<T> {
    public void inject(T instance, CreationalContext<T> ctx);
    public void postConstruct(T instance);
    public void preDestroy(T instance);
}
ProcessInjectionTarget<T>
public interface ProcessInjectionTarget<X> {
    public AnnotatedType<X> getAnnotatedType();
    public InjectionTarget<X> getInjectionTarget();
    public void setInjectionTarget(InjectionTarget<X> injectionTarget);
    public void addDefinitionError(Throwable t);
}

Bean Post Processors

class CdiCamelExtension implements Extension {
  Set<AnnotatedType<?>> camelBeans = new HashSet<>());

  void camelAnnotations(@Observes @WithAnnotations({BeanInject.class, (1)
      Consume.class, EndpointInject.class, Produce.class, PropertyInject.class})
      ProcessAnnotatedType<?> pat) {
        camelBeans.add(pat.getAnnotatedType());
  }

  <T> void camelBeansPostProcessor(@Observes ProcessInjectionTarget<T> pit) {
      if (camelBeans.contains(pit.getAnnotatedType())) (2)
        pit.setInjectionTarget(new CamelInjectionTarget<>(pit.getInjectionTarget()));
  }
}
1Detect all the types containing Camel annotations with @WithAnnotations
2Decorate the InjectionTarget corresponding to these types with a custom post-processor

InjectionTarget Decoration

class CamelInjectionTarget<T> implements InjectionTarget<T> {
    InjectionTarget<T> delegate;
    DefaultCamelBeanPostProcessor processor;

    CamelInjectionTarget(InjectionTarget<T> target) {
        delegate = target;
        processor = new DefaultCamelBeanPostProcessor();
    }

    @Override
    public void inject(T instance, CreationalContext<T> ctx) {
        delegate.inject(instance, ctx);
        processor.postProcessBeforeInitialization(instance); (1)
    }
}
1Call the Camel default bean post-processor after CDI injection

Camel DSL

from("jms:queue:{{input}}?transactionManager=#jtaTM")
  .id("Input Consumer")
  .onException().log("Rolling back message with ID ${header.JMSMessageID}")
    .rollback().id("Rollback Transaction")
    .end()
  .log("Receiving message with ID ${header.JMSMessageID}: ${body}")
  .choice()
    .when(header("JMSRedelivered").isEqualTo(Boolean.TRUE))
      .to("jms:queue:{{error}}?transactionManager=#jtaTM").id("Error Producer")
    .otherwise()
      .beanRef("transformer").id("Transformer")
      .to("murex:trade-repository").id("Trade Repository")
      .choice()
        .when(not(isInserted))
          .log("Error received: ${body}").id("Trade Repository Error")
          .throwException(new CamelExecutionException("Import Failed")))
        .otherwise()
          .log("Answer received: ${body}").id("Trade Repository Answer");

Camel AOP

Camel DSL Aspect Oriented Programming with CDI observer methods as pointcut and advice definitions
void interceptProcessor(@Observes @Before @Node("foo") Exchange exchange) {
    // intercept the exchange before processor with id "foo"
}
void interceptProcessorBody(@Observes @Node("foo") @Body String body) {
    // use Camel parameter binding annotations for the joint point context
}
void receive(@Observes(during=AFTER_SUCCESS) @Endpoint("bar") Exchange exchange) {
    // exchange sent to endpoint "bar" when the transaction is committed successfully
}

The ProcessObserverMethod Event

ObserverMethod<T>
public interface ObserverMethod<T> {
    public Class<?> getBeanClass();
    public Type getObservedType();
    public Set<Annotation> getObservedQualifiers();
    public Reception getReception();
    public TransactionPhase getTransactionPhase();
    public void notify(T event);
}
ProcessObserverMethod<T, X>
public interface ProcessObserverMethod<T, X> {
    public AnnotatedMethod<X> getAnnotatedMethod();
    public ObserverMethod<T> getObserverMethod();
    public void addDefinitionError(Throwable t);
}

Camel CDI Extension

Metrics CDI

Annotated types, alternatives, interceptors, producers

Dropwizard Metrics

Open-source Java library providing monitoring primitives like Counter, Gauge, Histogram, Meter, timer, …​
Provides a MetricRegistry that articulates modules and reporters
Defines annotations for AOP frameworks like Spring AOP, AspectJ, Guice (AOP Alliance) and CDI, e.g.:
class TimedMethodBean {

    @Timed
    void timedMethod() {
        // Timer name => TimedMethodBean.timedMethod
    }
}

The Bean Interface

Integrate the MetricRegistry as a CDI Bean
public interface Bean<T> extends Contextual<T>, BeanAttributes<T> {
    public Class<?> getBeanClass();
    public Set<InjectionPoint> getInjectionPoints();
    // Contextual<T>
    public T create(CreationalContext<T> creationalContext);
    public void destroy(T instance, CreationalContext<T> creationalContext);
    // BeanAttributes<T>
    public Set<Type> getTypes();
    public Set<Annotation> getQualifiers();
    public Class<? extends Annotation> getScope();
    public String getName();
    public Set<Class<? extends Annotation>> getStereotypes();
    public boolean isAlternative();
}

The MetricRegistry Bean

class MetricRegistryBean implements Bean<MetricRegistry> {

    public Set<Annotation> getQualifiers() {
        return Collections.unmodifiableSet(new HashSet<>(
            Arrays.asList(DefaultLiteral.INSTANCE, AnyLiteral.INSTANCE)));
    }

    public MetricRegistry create(CreationalContext<MetricRegistry> context) {
        return new MetricRegistry();
    }

    public Class<? extends Annotation> getScope() {
        return ApplicationScoped.class;
    }
    ...
}

The AfterBeanDiscovery Event

public interface AfterBeanDiscovery {
    public void addDefinitionError(Throwable t);
    public void addBean(Bean<?> bean);
    public void addObserverMethod(ObserverMethod<?> observerMethod);
    public void addContext(Context context);
    public <T> AnnotatedType<T> getAnnotatedType(Class<T> type, String id);
    public <T> Iterable<AnnotatedType<T>> getAnnotatedTypes(Class<T> type);
}

Add Beans Programmatically

class CdiMetricsExtension implements Extension {
  void defaultMetricRegistry(@Observes AfterBeanDiscovery abd, BeanManager manager) {
    if (manager.getBeans(MetricRegistry.class, AnyLiteral.INSTANCE).isEmpty()) (1)
        abd.addBean(new MetricRegistryBean()); (2)
    }
}
1Check if there is a bean of type MetricRegisty enabled
2If any add a default MetricRegisty bean implementation
Else, the end-user deployed MetricRegistry bean is used, e.g.:
@Produces
@ApplicationScoped
MetricRegistry customMetricRegistry() {
}

Add Interceptor Bindings Programmatically

Use interceptors for Metrics annotation AOP
class MetricsExtension implements Extension {

  <X> void metricsAnnotations(@Observes @WithAnnotations(Timed.class)
    ProcessAnnotatedType<X> pat) {
    Set<AnnotatedMethod<? super X>> decoratedMethods = new HashSet<>();
    for (AnnotatedMethod<? super X> method : pat.getAnnotatedType().getMethods()) {
      if (method.isAnnotationPresent(Timed.class)) {
          decoratedMethods.add(
            new AnnotatedMethodDecorator<>(method, new TimedBindingLiteral()));
      }
    }
    pat.setAnnotatedType(
      new AnnotatedTypeDecorator<>(pat.getAnnotatedType(), decoratedMethods));
  }
}

Bean Method Interceptors

@Interceptor
@TimedBinding
@Priority(Interceptor.Priority.LIBRARY_BEFORE)
class TimedInterceptor {

    @Inject MetricRegistry registry;

    @AroundInvoke
    Object timedMethod(InvocationContext context) throws Exception {
        String name = context.getMethod().getAnnotation(Timed.class).name();
        Timer timer = registry.timer(name);
        Timer.Context time = timer.time();
        try {
            return context.proceed();
        } finally {
            time.stop();
        }
    }
}

Complete Example

@Inject
private Meter hits; (1)

@Timed(name = "calls") (2)
public void cachedMethod() {
    if (hit) hits.mark();
}

@Produces @Metric(name = "cache-hits") (3)
private Gauge<Double> cacheHitRatioGauge(Meter hits, Timer calls) {
    return () -> calls.getOneMinuteRate() == 0 ? Double.NaN :
                 hits.getOneMinuteRate() / calls.getOneMinuteRate();
}
1Metric injection from the registry
2Method instrumentation with CDI interceptors
3Produce a custom Metric instance by composing others

References

Slides generated with Asciidoctor and DZSlides backend
Original slide template - Dan Allen & Sarah White
Camel CDI Extension - github.com/astefanutti/camel-cdi
Metrics CDI Extension - github.com/astefanutti/metrics-cdi

Antoine Sabot-Durand Antonin Stefanutti